package de.mrapp.materialview.library.drawable;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.ColorMatrix;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;

public class CircularProgressDrawable extends ShapeElement implements AnimatorValue.ValueUpdateListener {

    /**
     * The duration of the angle animation in milliseconds.
     */
    private static final long ANGLE_ANIMATION_DURATION = 2000L;

    /**
     * The duration of the sweep animation in milliseconds.
     */
    private static final long SWEEP_ANIMATION_DURATION = 600L;

    /**
     * The minimum angle of the sweep animation.
     */
    private static final int MIN_SWEEP_ANGLE = 30;

    /**
     * The number of degrees in a circle.
     */
    private static final int MAX_DEGREES = 360;

    /**
     * The color of the progress drawable.
     */
    private final int color;

    /**
     * The thickness of the progress drawable.
     */
    private final int thickness;


    /**
     * The paint, which is used for drawing.
     */
    private Paint paint;

    /**
     * The bounds of the drawable.
     */
    private RectFloat bounds;


    private AnimatorGroup animatorGroup;
    /**
     * The sweep animator.
     */
    private AnimatorValue sweepAnimator;

    /**
     * The angle animator.
     */
    private AnimatorValue angleAnimator;

    /**
     * The current angle of the sweep animation.
     */
    private float currentSweepAngle;

    /**
     * The current angle of the angle animation.
     */
    private float currentGlobalAngle;

    /**
     * The current offset of the angle animation.
     */
    private float currentGlobalAngleOffset;

    /**
     * True, if the progress bar is currently animated to be appearing, false. This value will
     * toggle each time the animation is repeated.
     */
    private boolean appearing;

    /**
     * Creates a new animated drawable, which is used by the view
     *
     * @param color     The color of the progress drawable as an {@link Integer} value
     * @param thickness The thickness of the progress drawable as an {@link Integer} value in pixels
     * @throws RuntimeException
     */
    public CircularProgressDrawable(final int color, final int thickness) {
        if (thickness < 1) {
            throw new RuntimeException("The thickness must be at least 1");
        }
        this.color = color;
        this.thickness = thickness;
        this.bounds = new RectFloat();
        animatorGroup = new AnimatorGroup();
        initializePaint();
        initializeAnimators();
    }

    /**
     * Initializes the paint, which is used for drawing.
     */
    private void initializePaint() {
        paint = new Paint();
        paint.setAntiAlias(true);

        paint.setStyle(Paint.Style.STROKE_STYLE);
        paint.setStrokeWidth(getThickness());
        paint.setColor(new Color(getColor()));
    }

    /**
     * Initializes the animators.
     */
    private void initializeAnimators() {
        initializeAngleAnimator();
        initializeSweepAnimator();
    }

    /**
     * Initializes the angle animator.
     */
    private void initializeAngleAnimator() {
        angleAnimator = new AnimatorValue();
        angleAnimator.setCurveType(Animator.CurveType.LINEAR);
        angleAnimator.setDuration(ANGLE_ANIMATION_DURATION);
        angleAnimator.setLoopedCount(-1);
        angleAnimator.setValueUpdateListener(this);
    }

    /**
     * Initializes the sweep animator.
     */
    private void initializeSweepAnimator() {
        sweepAnimator = new AnimatorValue();
        sweepAnimator.setCurveType(Animator.CurveType.DECELERATE);
        sweepAnimator.setDuration(SWEEP_ANIMATION_DURATION);
        sweepAnimator.setLoopedCount(-1);
        sweepAnimator.setValueUpdateListener(this);
    }


    /**
     * Returns the color of the progress drawable.
     *
     * @return The color of the progress drawable as an {@link Integer} value
     */
    public final int getColor() {
        return color;
    }

    /**
     * Returns the thickness of the progress drawable.
     *
     * @return The thickness of the progress drawable in pixels as an {@link Integer}
     */
    public final int getThickness() {
        return thickness;
    }

    @Override
    public void setAlpha(int alpha) {
        super.setAlpha(alpha);
        paint.setAlpha(alpha);
    }

    @Override
    public void drawToCanvas(Canvas canvas) {
        super.drawToCanvas(canvas);
        float startAngle = (float) ((double)currentGlobalAngle - (double) currentGlobalAngleOffset);
        float sweepAngle = currentSweepAngle;

        if (!appearing) {
            startAngle = (float) ((double)startAngle + (double) sweepAngle);
            sweepAngle = (float) ((double)MAX_DEGREES - (double) sweepAngle - (double) MIN_SWEEP_ANGLE);
        } else {
            sweepAngle += (double)MIN_SWEEP_ANGLE;
        }

        Arc arc = new Arc(startAngle, sweepAngle, false);
        canvas.drawArc(bounds, arc, paint);
    }

    public void start() {
        if(!animatorGroup.isRunning()) {
            animatorGroup.runParallel(sweepAnimator, angleAnimator);
        }
    }

    public void stop() {
        if(!animatorGroup.isRunning()) {
            animatorGroup.stop();
        }
    }

    @Override
    public void setBounds(Rect bounds) {
        super.setBounds(bounds);
        int width = bounds.right - bounds.left;
        int height = bounds.bottom - bounds.top;

        if (width < height) {
            int diff = height - width;
            this.bounds.left = (float) ((double)bounds.left + thickness / 2.0d + 0.5d);
            this.bounds.right = (float) ((double)bounds.right - thickness / 2.0d - 0.5d);
            this.bounds.top = (float) ((double)bounds.top + diff / 2.0d + thickness / 2.0d + 0.5d);
            this.bounds.bottom = (float) ((double)bounds.bottom - diff / 2.0d - thickness / 2.0d - 0.5d);
        } else {
            int diff = width - height;
            this.bounds.left = (float) ((double)bounds.left + diff / 2.0d + thickness / 2.0d + 0.5d);
            this.bounds.right = (float) ((double)bounds.right - diff / 2.0d - thickness / 2.0d - 0.5d);
            this.bounds.top = (float) ((double)bounds.top + thickness / 2.0d + 0.5d);
            this.bounds.bottom = (float) ((double)bounds.bottom - thickness / 2.0d - 0.5d);
        }
    }

    @Override
    public void onUpdate(AnimatorValue animatorValue, float percent) {
        if (animatorValue == angleAnimator) {
            currentGlobalAngle = percent * MAX_DEGREES;
        } else if (animatorValue == sweepAnimator) {
            int maxDegree = MAX_DEGREES - MIN_SWEEP_ANGLE * 2;
            currentSweepAngle = percent * maxDegree;
        }
    }
}
